package org.jmqtt.broker.remoting.netty;

import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.apache.commons.lang3.StringUtils;
import org.jmqtt.broker.common.config.NettyConfig;
import org.jmqtt.broker.common.log.JmqttLogger;
import org.jmqtt.broker.common.log.LogUtil;
import org.jmqtt.broker.remoting.session.ClientSession;
import org.jmqtt.broker.remoting.session.ConnectManager;
import org.jmqtt.broker.remoting.util.NettyUtil;
import org.jmqtt.broker.remoting.util.RemotingHelper;
import org.slf4j.Logger;

import java.util.Date;

public class NettyConnectHandler extends ChannelDuplexHandler {

    private static final Logger log = JmqttLogger.remotingLog;

    private NettyEventExecutor eventExecutor;
    private NettyConfig        nettyConfig;

    public NettyConnectHandler(NettyEventExecutor nettyEventExecutor, NettyConfig nettyConfig){
        this.eventExecutor = nettyEventExecutor;
        this.nettyConfig = nettyConfig;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx){
        final String remoteAddr = RemotingHelper.getRemoteAddr(ctx.channel());
        LogUtil.debug(log, "[ChannelActive] -> 通道连接... addr = {}", remoteAddr);
        this.eventExecutor.putNettyEvent(new NettyEvent(remoteAddr, NettyEventType.CONNECT, ctx.channel()));
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx){
        final String remoteAddr = RemotingHelper.getRemoteAddr(ctx.channel());
        LogUtil.debug(log, "[ChannelInactive] -> 通道关闭... addr = {}", remoteAddr);
        this.eventExecutor.putNettyEvent(new NettyEvent(remoteAddr, NettyEventType.CLOSE, ctx.channel()));
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        String clientId = NettyUtil.getClientId(ctx.channel());
        if (StringUtils.isEmpty(clientId)) {
            LogUtil.warn(log, "[HEART_BEAT] -> 根据通道未查到客户端id, 无法判断心跳...");
            return;
        }
        ClientSession client = ConnectManager.getInstance().getClient(clientId);
        long lossConnectTime = client.getLossConnectTime();

        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state().equals(IdleState.READER_IDLE)) {
                // 设备离线，更改设备状态，增加离线操作日志
                lossConnectTime++;
                client.setLossConnectTime(lossConnectTime);
                LogUtil.info(log, "[HEART_BEAT] -> 客户端 『{}』 离线 {} 分钟...", client.getClientId(), lossConnectTime);
                if (lossConnectTime >= nettyConfig.getMaxLossConnectTime()) {
                    // 客户端断连10分钟
                    final String remoteAddr = RemotingHelper.getRemoteAddr(ctx.channel());
                    LogUtil.warn(log, "[HEART_BEAT] -> 『{}』 心跳异常，服务器将主动关闭 『{}』 链路，原因：{}s 没收到消息了...",
                            remoteAddr, client.getClientId(), (lossConnectTime * 60));
                    RemotingHelper.closeChannel(ctx.channel());
                    this.eventExecutor.putNettyEvent(new NettyEvent(remoteAddr, NettyEventType.IDLE, ctx.channel()));
                }
            } else {
                //复位
                LogUtil.info(log, "[HEART_BEAT] -> 客户端 『{}』 恢复连接...", client.getClientId(), lossConnectTime);
                client.setLossConnectTime(0);
                client.setInitConnectDate(new Date());
                super.userEventTriggered(ctx, evt);
            }
        } else {
            //复位
            LogUtil.info(log, "[HEART_BEAT] -> 客户端 『{}』 恢复连接...", client.getClientId(), lossConnectTime);
            client.setLossConnectTime(0);
            client.setInitConnectDate(new Date());
            super.userEventTriggered(ctx, evt);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
        String remoteAddr = RemotingHelper.getRemoteAddr(ctx.channel());
        LogUtil.warn(log,"Channel caught Exception remotingAddr:{},cause:{}", remoteAddr,cause);
        RemotingHelper.closeChannel(ctx.channel());
        this.eventExecutor.putNettyEvent(new NettyEvent(remoteAddr,NettyEventType.EXCEPTION,ctx.channel()));
    }
}
